#include <iostream>
#include <iomanip>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <unistd.h>
#include <getopt.h>
#include <omp.h>
using namespace std;

#include "body.h"

int main(int argc, char *argv[])
{
	unsigned int i,j, k;
	double start, stop, runtime;

	
	/* parse arguments */
	int c;
	unsigned int optBodys		= 5;
	unsigned int optIterations	= 100;
	unsigned int optThreads		= 2;
	      double optDT			= 2000.0f;
	        bool optPrint		= false;
	        bool optDebug		= false;

	if (argc >= 1+2+2)
	{
		while ((c = getopt(argc, argv, "b:i:t:d:p?")) != -1)
		{
			switch (c)
			{
				// required
				case 'b':
					optBodys = atoi(optarg) >= 1 ? atoi(optarg) : optBodys; break;
				case 'i':
					optIterations = atoi(optarg) >= 1 ? atoi(optarg) : optIterations; break;
				// optional
				case 't':
					optThreads = atoi(optarg) >= 1 ? atoi(optarg) : optThreads; break;
				case 'd':
					optDT = atoi(optarg) >= 1 ? atoi(optarg) : optDT; break;
				case 'p':
					optPrint = true; break;
				case '?':
					optDebug = true; break;
				default:
					break;
			}
		}
	}
	else
	{
		printf("Syntax Error!\n");
		printf("Usage: %s -b bodys -i iterations [OPTIONS]\n", argv[0]);
		printf("Options:\n");
		printf("  -t <value>  set no. of threads (defaut: 2)");
		printf("  -d <value>  adjust DT (default: 2000)\n");
		printf("  -p          print coordinates (default: off)\n");
		printf("  -?          print debug infos (default: off)\n");
		
		return -1;
	}

	/* sanity checks */
	if (optThreads > optBodys) return -2;
	if (optIterations > 10000) return -3;

	if (optDebug)
	{
		std::cout << "# bodys: " << optBodys << std::endl;
		std::cout << "# iterations: " << optIterations << std::endl;
		std::cout << "# dt: " << optDT << std::endl;
		std::cout << "# threads: " << optThreads << std::endl;
	}

	/* allocate objects */
	Body *bodys = new Body[optBodys]();
	//std::cout << "# bodys: " << bodys->getBodyCount() << std::endl;
	
	/* init bodys */
	for (i=0; i<optBodys; i++)
	{
		bodys[i].setDt(optDT);
		bodys[i].setMass(rand()%100 + 1);
		bodys[i].setPos(rand()%10-5, rand()%10-5);
	}
	
	/* perform simulation */
	omp_set_num_threads(optThreads);
	start = omp_get_wtime();
	for (i=0; i<optIterations; i++)
	{
		#pragma omp parallel private(j, k) shared (bodys)
		{
			int id = omp_get_thread_num();
			int nThreads = omp_get_num_threads();

			unsigned int start = id * optBodys / nThreads;
			unsigned int   end = (id+1) * optBodys / nThreads - 1;
			if (id == nThreads-1) end = optBodys - 1;

			if (optDebug)
			{
				static bool once = false;
				if (!once)
				{
					printf("# %2d: start=%i end=%i\n", id, start, end);
					#pragma omp barrier
					once=true;
				}
			}

			for (j=start; j<=end; j++)
			{
				#pragma omp master
				if (optPrint) printf("%12.9lf %12.9lf   ", bodys[j].posx, bodys[j].posy);

				//printf("# %2d: fx=%12.9lf, fy=%12.9lf \n", id, bodys[j].fx, bodys[j].fy);
				bodys[j].fx = 0.0;
				bodys[j].fy = 0.0;
				for (k=0; k<optBodys; k++)
				{
					//if (j == k) continue; // skip self
					if (j != k)
						bodys[j].calcNewForce(&bodys[k]);
				}
			}

			#pragma omp master
			if (optPrint) printf("\n");

			#pragma omp barrier
			for (j=start; j<=end; j++)
			{
				bodys[j].calcNewPos();
			}
		}
	}
	stop = omp_get_wtime();

	runtime = stop - start;
	if (optDebug)
	{
		std::cout << "# runtime: " << runtime << std::endl;
		std::cout << "# body-body-interactions/s : " << optBodys*(optBodys-1)/runtime << std::endl;
	}
	std::cout	<< std::fixed << std::setprecision(3)
				<< optThreads << "\t"
				<< optBodys << "\t"
				<< runtime << "\t"
				<< floor(optBodys*(optBodys-1)/runtime) << "  \t"
				<< std::endl;

	delete [] bodys;
	return EXIT_SUCCESS;
}

